import java.util.ArrayList;
import java.util.concurrent.Semaphore;

import javax.swing.JFrame;


public class BaseStation extends Thread {
	private int nbReceivers;
	private int lengthChips;
	private int lengthMessage;
	private int nbFrames = 1;
	private int percentageError;
	private int[] cellCode;
	private int[] networkCode; 
	private GoldCode gold;
	private int[] goldCode;
	
	ArrayList<Receiver> receiversList;
	HadamardMatrix hadamardInitialMatrix;
	boolean[] codeOVSFBool;
	
	int[][] messages;
	int[][][] spreadMessages;
	int[][] sumSpreadMessages;
	int[][] signalToSend;
	boolean errorPresent = false;
	
	
	UMTS frame;
	
	Semaphore[] semOk;
	boolean[] receiversError;
	boolean[] receiversOk;
	
	public BaseStation(int nbReceivers, int lengthChips, int lengthMessage, int[] networkCode, ArrayList<Receiver> receiversList, int percentageError, UMTS frame){
		this.frame = frame;
		
		this.semOk = new Semaphore[nbReceivers];
		receiversError = new boolean[nbReceivers];
		receiversOk = new boolean[nbReceivers];
		
		for(int i=0;i<nbReceivers;i++){
			this.semOk[i] = new Semaphore(0, true);
			receiversError[i] = false;
			receiversOk[i]= false;
		}
		
		this.nbReceivers = nbReceivers;
		this.lengthChips = lengthChips;
		this.lengthMessage = lengthMessage;
		this.networkCode = networkCode;
		this.receiversList = receiversList;
		this.percentageError = percentageError;
		
		
		
		hadamardInitialMatrix = new HadamardMatrix(nbReceivers);
		messages = new int[nbReceivers][lengthMessage];
		cellCode = new int[3];
		for(int i=0; i<3;i++){
			cellCode[i] = (int) (Math.random() * (2));		
		}
		this.networkCode = networkCode;
		
		hadamardMatrixGeneration(hadamardInitialMatrix, 1);
		goldCodeGeneration();
	}
	
	public void hadamardMatrixGeneration(HadamardMatrix hadamardMatrix, int type){
		if(type==1){
			hadamardMatrix.generate();
		}
		else if(type==2){
			hadamardMatrix.generate2();
		}
		
		//creation table to know if a code is already used by an user
		codeOVSFBool = new boolean[hadamardMatrix.getNbLines()];
		for(int i=0; i<hadamardMatrix.getNbLines(); i++){
			codeOVSFBool[i] = false;
		}
		
		int lengthSpreadMessage = lengthMessage * hadamardMatrix.getNbLines();
		if(lengthSpreadMessage > lengthChips){
			nbFrames = lengthSpreadMessage / lengthChips;
		}
		
		spreadMessages = new int[nbFrames][nbReceivers][lengthChips];
	}
	
	public void goldCodeGeneration(){
		gold = new GoldCode();
		goldCode = gold.goldCodeRandomly();
		transformeNonZero(goldCode);
	}
	
	public void messageGeneration(){
		//randomly messages creation
		for(int i=0; i<nbReceivers; i++){
			int[] message = new int[lengthMessage];
			String mess ="";
			for(int j=0; j<lengthMessage; j++){
				message[j] = (int) (Math.random() * (2));
				mess += message[j];
			}
			messages[i] = message;
			receiversList.get(i).setOriginalMess(messages[i]);
			frame.print("Base Station : Signal sent to the user "+i+" : "+mess);
			transformeNonZero(messages[i]);
		}
	}
	
	public void spreading(){	
		for(int i=0; i<nbReceivers; i++){
			for(int m=0; m<nbFrames;m++){	
				//computation product messages * associated OVSFcode
				int l=0;
				int cpt = 0;
				if(lengthMessage * receiversList.get(i).getSpreadCode().length < lengthChips){
					cpt = lengthChips - lengthMessage * receiversList.get(i).getSpreadCode().length;
					for(int j=0; j<cpt; j++){
						spreadMessages[m][i][l] = 0;
						l++;
					}
				}
			}
			int l=0;
			int nbFrame = 0;
			for(int j=0; j<messages[i].length; j++){
				for(int k=0; k<receiversList.get(i).getSpreadCode().length; k++){
					spreadMessages[nbFrame][i][l] = messages[i][j] * receiversList.get(i).getSpreadCode()[k];
					l++;
					if(l==lengthChips){
						l=0;
						nbFrame++;
					}
				}
			}
		}
	}
	
	public void sumProducts(){	
		sumSpreadMessages = new int [nbFrames][lengthChips];
		for(int m=0; m<nbFrames;m++){
			for(int j=0; j<nbReceivers; j++){
				for(int i=0; i<lengthChips; i++){
					sumSpreadMessages[m][i] += spreadMessages[m][j][i];
				}
			}
		}
	}
	
	public void scrambling(){
		signalToSend = new int [nbFrames][lengthChips];
		int mod = 0;
		int indice = 0;
		
		for(int m=0; m<nbFrames;m++){
			mod = (int) sumSpreadMessages[m].length / goldCode.length;
			for(int i=0;i<sumSpreadMessages[m].length;i++){
				indice = i % mod;
				signalToSend[m][i] = sumSpreadMessages[m][i] * goldCode[indice];
			}
		}
	}
	
	public void simulateErrors(){
		String temp;
		for(int m=0; m<nbFrames;m++){
			int nbErrors = signalToSend[m].length*percentageError/100;
			
			ArrayList<Integer> errorsPosition;
			int ctpErrors = 0;
			Integer pos = null;
			Integer pos1 = null;
			int lower = 0;
			int higher = signalToSend[0].length;
			
			errorsPosition = new ArrayList<Integer>();
			for (int i=0;i<nbErrors;i++){
				pos = (int)(Math.random() * (higher-lower)) + lower;
				
				
				while(errorsPosition.contains(pos)){
					pos = (int)(Math.random() * (higher-lower)) + lower;
				}
				errorsPosition.add(pos);
			}
			
			for(int j=0;j<signalToSend[m].length;j++){
				if(errorsPosition.contains(j)){
					pos1 = (int)(Math.random() * (higher-lower)) + lower;
					signalToSend[m][j] = signalToSend[m][pos1];
					ctpErrors++;
				}
			}
		}
		
	}
	
	
	public void sendSignal(){
		for(int i=0; i<nbReceivers; i++){
			receiversList.get(i).setReceivedSignal(signalToSend);
			
			receiversList.get(i).releaseSem();
			for(int m=0; m<nbFrames;m++){
				receiversList.get(i).releaseSem();
			}
		}
	}

	public void sendAfterError(){
		errorPresent = false;
		
		//wait that all receivers have finished
		for(int i=0;i<nbReceivers;i++){
			if(!receiversOk[i]){
				try {
					semOk[i].acquire();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		int numRec = 0;
		for(int i=0;i<nbReceivers;i++){
			if(receiversError[i]){
					errorPresent = true;
					numRec = i;
			}
		}
		if(!errorPresent){
			frame.print("END");
			this.stop();
		}
		else{
			
			//if a receiver has an error
			frame.print("Base Station : Resend because errors during the transmission");
	
			int indice = 4;
			int cpt = 4;
			
			while(receiversList.get(numRec).getSpreadCode().length > cpt){
				indice++;
				cpt *= 2;
			}
			
			
			HadamardMatrix newHadamardMatrix = new HadamardMatrix(indice);
			hadamardMatrixGeneration(newHadamardMatrix, 2);
			for(int i=0; i<nbReceivers; i++){
				receiversList.get(i).setSpreadCode(this.getOVSFCode(newHadamardMatrix));
			}
			
			spreading();
			sumProducts();
			scrambling();
			simulateErrors();
			frame.print("Base Station : Signal sent again to the users -- Hadamard Matrice size : "+newHadamardMatrix.getNbLines());
			for(int i=0; i<nbReceivers; i++){
				if(receiversError[i]){
					receiversList.get(i).setReceivedSignal(signalToSend);
					receiversList.get(i).setNbFrames(nbFrames);
					for(int m=0; m<nbFrames;m++){
						receiversList.get(i).releaseSem();
					}
					receiversError[i] = false;
				}
			}
			
		}
	}
	
	
	public void run(){
		
		messageGeneration();
		frame.print("Base Station : Messages Generated");
		frame.print("Base Station : Spreading");
		spreading();
		
		sumProducts();
		
		frame.print("Base Station : Scrambling");
		scrambling();
		
		simulateErrors();
		
		sendSignal();
		
		do{
			sendAfterError();
		}while(errorPresent);
	}

	public ArrayList<Receiver> getReceiversList() {
		return receiversList;
	}


	public void setReceiversList(ArrayList<Receiver> receiversList) {
		this.receiversList = receiversList;
	}


	public HadamardMatrix getHadamardInitialMatrix() {
		return hadamardInitialMatrix;
	}


	public void setHadamardInitialMatrix(HadamardMatrix hadamardInitialMatrix) {
		this.hadamardInitialMatrix = hadamardInitialMatrix;
	}


	public boolean[] getCodeOVSFBool() {
		return codeOVSFBool;
	}


	public void setCodeOVSFBool(boolean[] codeOVSFBool) {
		this.codeOVSFBool = codeOVSFBool;
	}


	public int[][] getMessages() {
		return messages;
	}


	public void setMessages(int[][] messages) {
		this.messages = messages;
	}


	public int[][][] getSpreadMessages() {
		return spreadMessages;
	}


	public void setSpreadMessages(int[][][] spreadMessages) {
		this.spreadMessages = spreadMessages;
	}
	
	public int[][] getSumSpreadMessages() {
		return sumSpreadMessages;
	}

	public void setSumSpreadMessages(int[][] sumSpreadMessages) {
		this.sumSpreadMessages = sumSpreadMessages;
	}
	
	public int getNbFrames() {
		return nbFrames;
	}

	public void setNbFrames(int nbFrames) {
		this.nbFrames = nbFrames;
	}

	public int[] getCellCode() {
		return cellCode;
	}

	public void setCellCode(int[] cellCode) {
		this.cellCode = cellCode;
	}
	
	public int[] getOVSFCode(HadamardMatrix hadamardMatrix){
		int lower = 0;
		int higher = hadamardMatrix.getNbLines();

		//chose of OVSH code randomly
		int randomlyNbLine;
		while(codeOVSFBool[randomlyNbLine = (int)(Math.random() * (higher-lower)) + lower]);
		codeOVSFBool[randomlyNbLine] = true;
		return hadamardMatrix.getLine(randomlyNbLine);
	}
	
	public int[] getMessage(int i){
		return messages[i];
	}
	
	

	public int[] getGoldCode() {
		return goldCode;
	}

	int[] transformeNonZero(int[] message){
		for(int i=0; i<message.length; i++){
			if(message[i] == 0){
				message[i] = -1;
			}
		}
		return message;
	}

	public void setReceiversError(int i, boolean b){
		receiversError[i] = b;
	}
	
	public void setReceiversOk(int i, boolean b){
		receiversOk[i] = b;
	}

	public void releaseSemOk(int i){
			this.semOk[i].release();	
	}
}
